WebCodecsã®ãã¯ãŒãè§£ãæŸã€ïŒVideoFrameãã¬ãŒã³ã䜿çšãããããªãã¬ãŒã ããŒã¿ãžã®ã¢ã¯ã»ã¹ãšæäœã«é¢ããå æ¬çãªã¬ã€ãã§ãããã¯ã»ã«ãã©ãŒããããã¡ã¢ãªã¬ã€ã¢ãŠãããã©ãŠã¶ã§ã®é«åºŠãªãããªåŠçã®ããã®å®çšçãªãŠãŒã¹ã±ãŒã¹ãåŠã³ãŸãã
WebCodecs VideoFrameãã¬ãŒã³ïŒãããªãã¬ãŒã ããŒã¿ãžã®ã¢ã¯ã»ã¹ã®æ·±æã
WebCodecsã¯ããŠã§ãããŒã¹ã®ã¡ãã£ã¢åŠçã«ããããã©ãã€ã ã·ããã象城ããŠããŸããããã¯ã¡ãã£ã¢ã®æ§æèŠçŽ ãžã®äœã¬ãã«ã¢ã¯ã»ã¹ãæäŸããéçºè
ããã©ãŠã¶ã§çŽæ¥ãé«åºŠãªã¢ããªã±ãŒã·ã§ã³ãäœæã§ããããã«ããŸããWebCodecsã®æã匷åãªæ©èœã®äžã€ãVideoFrameãªããžã§ã¯ãã§ããããã®äžã«ããVideoFrameãã¬ãŒã³ã¯ãããªãã¬ãŒã ã®çã®ãã¯ã»ã«ããŒã¿ãå
¬éããŸãããã®èšäºã§ã¯ãé«åºŠãªãããªæäœã®ããã«VideoFrameãã¬ãŒã³ãçè§£ããæŽ»çšããããã®å
æ¬çãªã¬ã€ããæäŸããŸãã
VideoFrameãªããžã§ã¯ãã®çè§£
ãã¬ãŒã³ã«ã€ããŠè©³ããèŠãŠããåã«ãVideoFrameãªããžã§ã¯ãèªäœã«ã€ããŠããããããŸããããVideoFrameã¯ãããªã®åäžãã¬ãŒã ã衚ããŸããããã«ã¯ããã³ãŒããããïŒãŸãã¯ãšã³ã³ãŒããããïŒãããªããŒã¿ããã¿ã€ã ã¹ã¿ã³ãããã¥ã¬ãŒã·ã§ã³ããã©ãŒãããæ
å ±ãªã©ã®é¢é£ã¡ã¿ããŒã¿ãšãšãã«ã«ãã»ã«åãããŠããŸããVideoFrame APIã¯ä»¥äžã®ããã®ã¡ãœãããæäŸããŸãïŒ
- ãã¯ã»ã«ããŒã¿ã®èªã¿åãïŒããã§ãã¬ãŒã³ãç»å ŽããŸãã
- ãã¬ãŒã ã®ã³ããŒïŒæ¢åã®
VideoFrameãªããžã§ã¯ãããæ°ãããã®ãäœæããŸãã - ãã¬ãŒã ã®ã¯ããŒãºïŒãã¬ãŒã ãä¿æããŠããåºç€ãšãªããªãœãŒã¹ãè§£æŸããŸãã
VideoFrameãªããžã§ã¯ãã¯ãéåžžã¯VideoDecoderã«ãã£ãŠãã³ãŒãåŠçäžã«äœæãããããã«ã¹ã¿ã ãã¬ãŒã ãäœæããéã«æåã§äœæãããŸãã
VideoFrameãã¬ãŒã³ãšã¯äœãïŒ
VideoFrameã®ãã¯ã»ã«ããŒã¿ã¯ãç¹ã«YUVã®ãããªãã©ãŒãããã§ã¯ãè€æ°ã®ãã¬ãŒã³ã«ç·šæãããããšããããããŸããåãã¬ãŒã³ã¯ç»åã®ç°ãªãã³ã³ããŒãã³ãã衚ããŸããäŸãã°ãYUV420ãã©ãŒãããã§ã¯ã3ã€ã®ãã¬ãŒã³ããããŸãïŒ
- YïŒèŒåºŠïŒïŒç»åã®æããïŒèŒåºŠïŒã衚ããŸãããã®ãã¬ãŒã³ã«ã¯ã°ã¬ãŒã¹ã±ãŒã«æ å ±ãå«ãŸããŸãã
- UïŒCbïŒïŒéè²å·®ã¯ããæåã衚ããŸãã
- VïŒCrïŒïŒèµ€è²å·®ã¯ããæåã衚ããŸãã
RGBãã©ãŒãããã¯ãäžèŠãããšåçŽã«èŠããŸãããå Žåã«ãã£ãŠã¯è€æ°ã®ãã¬ãŒã³ã䜿çšããããšããããŸãããã¬ãŒã³ã®æ°ãšãã®æå³ã¯ãVideoFrameã®VideoPixelFormatã«å®å
šã«äŸåããŸãã
ãã¬ãŒã³ã䜿çšããå©ç¹ã¯ãç¹å®ã®è²æåãžã®å¹ççãªã¢ã¯ã»ã¹ãšæäœãå¯èœã«ãªãããšã§ããäŸãã°ãè²ïŒUããã³Vãã¬ãŒã³ïŒã«åœ±é¿ãäžããã«ãèŒåºŠïŒYãã¬ãŒã³ïŒã ãã調æŽãããå ŽåããããŸãã
VideoFrameãã¬ãŒã³ãžã®ã¢ã¯ã»ã¹ïŒAPI
VideoFrame APIã¯ããã¬ãŒã³ããŒã¿ã«ã¢ã¯ã»ã¹ããããã«ä»¥äžã®ã¡ãœãããæäŸããŸãïŒ
copyTo(destination, options)ïŒVideoFrameã®å 容ããå¥ã®VideoFrameãCanvasImageBitmapããŸãã¯ArrayBufferViewãªã©ã®å®å ã«ã³ããŒããŸããoptionsãªããžã§ã¯ãã¯ãã©ã®ãã¬ãŒã³ãã©ã®ããã«ã³ããŒããããå¶åŸ¡ããŸããããããã¬ãŒã³ã¢ã¯ã»ã¹ã®äž»èŠãªã¡ã«ããºã ã§ãã
copyToã¡ãœããã®optionsãªããžã§ã¯ãã䜿çšãããšããããªãã¬ãŒã ããŒã¿ã®ã¬ã€ã¢ãŠããšã¿ãŒã²ãããæå®ã§ããŸããäž»ãªããããã£ã¯æ¬¡ã®ãšããã§ãïŒ
formatïŒã³ããŒãããããŒã¿ã®ç®çã®ãã¯ã»ã«ãã©ãŒããããããã¯å ã®VideoFrameãšåããã©ãŒãããã§ããç°ãªããã©ãŒãããïŒäŸïŒYUVããRGBãžã®å€æïŒã§ãããŸããŸãããcodedWidthãšcodedHeightïŒãããªãã¬ãŒã ã®å¹ ãšé«ãããã¯ã»ã«åäœã§æå®ããŸããlayoutïŒã¡ã¢ãªå ã®åãã¬ãŒã³ã®ã¬ã€ã¢ãŠããèšè¿°ãããªããžã§ã¯ãã®é åãé åå ã®åãªããžã§ã¯ãã¯ä»¥äžãæå®ããŸãïŒoffsetïŒããŒã¿ãããã¡ã®å é ãããã¬ãŒã³ããŒã¿ã®éå§ãŸã§ã®ãªãã»ããïŒãã€ãåäœïŒãstrideïŒãã¬ãŒã³å ã®åè¡ã®éå§éã®ãã€ãæ°ãããã¯ããã£ã³ã°ãåŠçããããã«éåžžã«éèŠã§ãã
YUV420 VideoFrameãçã®ãããã¡ã«ã³ããŒããäŸãèŠãŠã¿ãŸãããïŒ
async function copyYUV420ToBuffer(videoFrame, buffer) {
const width = videoFrame.codedWidth;
const height = videoFrame.codedHeight;
// YUV420ã«ã¯Y, U, Vã®3ã€ã®ãã¬ãŒã³ããããŸã
const yPlaneSize = width * height;
const uvPlaneSize = width * height / 4;
const layout = [
{ offset: 0, stride: width }, // Yãã¬ãŒã³
{ offset: yPlaneSize, stride: width / 2 }, // Uãã¬ãŒã³
{ offset: yPlaneSize + uvPlaneSize, stride: width / 2 } // Vãã¬ãŒã³
];
await videoFrame.copyTo(buffer, {
format: 'I420',
codedWidth: width,
codedHeight: height,
layout: layout
});
videoFrame.close(); // ãªãœãŒã¹è§£æŸã®ããéèŠ
}
解説ïŒ
widthãšheightã«åºã¥ããŠåãã¬ãŒã³ã®ãµã€ãºãèšç®ããŸããYã¯ãã«è§£å床ã§ãããUãšVã¯ãµããµã³ããªã³ã°ãããŠããŸãïŒ4:2:0ïŒãlayouté åã¯ã¡ã¢ãªã¬ã€ã¢ãŠããå®çŸ©ããŸããoffsetã¯åãã¬ãŒã³ããããã¡å ã§ã©ãããå§ãŸãããæå®ããstrideã¯ãã®ãã¬ãŒã³ã®æ¬¡ã®è¡ã«ç§»åããããã«ãžã£ã³ããããã€ãæ°ãæå®ããŸããformatãªãã·ã§ã³ã¯ãäžè¬çãªYUV420ãã©ãŒãããã§ãã'I420'ã«èšå®ãããŸãã- éèŠãªç¹ãšããŠãã³ããŒåŸã«ãªãœãŒã¹ãè§£æŸããããã«
videoFrame.close()ãåŒã³åºãããŸãã
ãã¯ã»ã«ãã©ãŒãããïŒå¯èœæ§ã®äžç
ãã¯ã»ã«ãã©ãŒãããã®çè§£ã¯ãVideoFrameãã¬ãŒã³ãæ±ãäžã§äžå¯æ¬ ã§ããVideoPixelFormatã¯ããããªãã¬ãŒã å
ã§è²æ
å ±ãã©ã®ããã«ãšã³ã³ãŒããããããå®çŸ©ããŸãã以äžã¯ãééããå¯èœæ§ã®ããäžè¬çãªãã¯ã»ã«ãã©ãŒãããã®äžéšã§ãïŒ
- I420 (YUV420p)ïŒYãUãVæåãå¥ã ã®ãã¬ãŒã³ã«æ ŒçŽããããã¬ãŒããŒYUVãã©ãŒããããUãšVã¯æ°Žå¹³æ¹åãšåçŽæ¹åã®äž¡æ¹ã§2åã®1ã«ãµããµã³ããªã³ã°ãããŸããéåžžã«äžè¬çã§å¹ççãªãã©ãŒãããã§ãã
- NV12 (YUV420sp)ïŒYãäžã€ã®ãã¬ãŒã³ã«æ ŒçŽãããUãšVæåã2çªç®ã®ãã¬ãŒã³ã«ã€ã³ã¿ãŒãªãŒããããã»ããã¬ãŒããŒYUVãã©ãŒãããã
- RGBAïŒèµ€ãç·ãéãããã³ã¢ã«ãã¡æåãåäžã®ãã¬ãŒã³ã«æ ŒçŽãããéåžžã¯åæå8ãããïŒãã¯ã»ã«ããã32ãããïŒã§ããæåã®é åºã¯ç°ãªãå ŽåããããŸãïŒäŸïŒBGRAïŒã
- RGB565ïŒèµ€ãç·ãéã®æåãåäžã®ãã¬ãŒã³ã«æ ŒçŽãããèµ€ã«5ããããç·ã«6ããããéã«5ãããïŒãã¯ã»ã«ããã16ãããïŒã䜿çšãããŸãã
- GRAYSCALEïŒåãã¯ã»ã«ã«å¯ŸããŠåäžã®èŒåºŠïŒæããïŒå€ãæã€ã°ã¬ãŒã¹ã±ãŒã«ç»åã衚ããŸãã
VideoFrame.formatããããã£ã¯ãç¹å®ã®ãã¬ãŒã ã®ãã¯ã»ã«ãã©ãŒãããã瀺ããŸãããã¬ãŒã³ã«ã¢ã¯ã»ã¹ããåã«ããã®ããããã£ã確èªããããã«ããŠãã ããããµããŒããããŠãããã©ãŒãããã®å®å
šãªãªã¹ãã«ã€ããŠã¯ãWebCodecs仿§ãåç
§ããŠãã ããã
å®çšçãªãŠãŒã¹ã±ãŒã¹
VideoFrameãã¬ãŒã³ãžã®ã¢ã¯ã»ã¹ã¯ããã©ãŠã¶ã§ã®é«åºŠãªãããªåŠçã«å¹
åºãå¯èœæ§ãéããŸãã以äžã«ããã€ãã®äŸãæããŸãïŒ
1. ãªã¢ã«ã¿ã€ã ãããªãšãã§ã¯ã
VideoFrameã®ãã¯ã»ã«ããŒã¿ãæäœããããšã§ããªã¢ã«ã¿ã€ã ã®ãããªãšãã§ã¯ããé©çšã§ããŸããäŸãã°ãRGBAãã¬ãŒã ã®åãã¯ã»ã«ã®RãGãBæåãå¹³ååãã3ã€ã®æåãã¹ãŠããã®å¹³åå€ã«èšå®ããããšã§ãã°ã¬ãŒã¹ã±ãŒã«ãã£ã«ã¿ãå®è£
ã§ããŸããã»ãã¢èª¿ã®ãšãã§ã¯ããäœæããããæãããã³ã³ãã©ã¹ãã調æŽãããããããšãã§ããŸãã
async function applyGrayscale(videoFrame) {
const width = videoFrame.codedWidth;
const height = videoFrame.codedHeight;
const buffer = new ArrayBuffer(width * height * 4); // RGBA
const rgba = new Uint8ClampedArray(buffer);
await videoFrame.copyTo(rgba, {
format: 'RGBA',
codedWidth: width,
codedHeight: height
});
for (let i = 0; i < rgba.length; i += 4) {
const r = rgba[i];
const g = rgba[i + 1];
const b = rgba[i + 2];
const gray = (r + g + b) / 3;
rgba[i] = gray; // èµ€
rgba[i + 1] = gray; // ç·
rgba[i + 2] = gray; // é
}
// 倿ŽãããããŒã¿ããæ°ããVideoFrameãäœæ
const newFrame = new VideoFrame(rgba, {
format: 'RGBA',
codedWidth: width,
codedHeight: height,
timestamp: videoFrame.timestamp,
duration: videoFrame.duration
});
videoFrame.close(); // å
ã®ãã¬ãŒã ãè§£æŸ
return newFrame;
}
2. ã³ã³ãã¥ãŒã¿ããžã§ã³ã¢ããªã±ãŒã·ã§ã³
VideoFrameãã¬ãŒã³ã¯ãã³ã³ãã¥ãŒã¿ããžã§ã³ã¿ã¹ã¯ã«å¿
èŠãªãã¯ã»ã«ããŒã¿ãžã®çŽæ¥ã¢ã¯ã»ã¹ãæäŸããŸãããã®ããŒã¿ã䜿çšããŠãç©äœæ€åºãé¡èªèãã¢ãŒã·ã§ã³ãã©ããã³ã°ãªã©ã®ã¢ã«ãŽãªãºã ãå®è£
ã§ããŸããããã©ãŒãã³ã¹ãéèŠãªã³ãŒãã»ã¯ã·ã§ã³ã«ã¯WebAssemblyãæŽ»çšã§ããŸãã
äŸãã°ãã«ã©ãŒã®VideoFrameãã°ã¬ãŒã¹ã±ãŒã«ã«å€æãããã®åŸããšããžæ€åºã¢ã«ãŽãªãºã ïŒäŸïŒSobelãªãã¬ãŒã¿ïŒãé©çšããŠç»åå
ã®ãšããžãèå¥ããããšãã§ããŸããããã¯ç©äœèªèã®ååŠçã¹ããããšããŠäœ¿çšã§ããŸãã
3. ãããªç·šéãšåæ
VideoFrameãã¬ãŒã³ã䜿çšããŠãã¯ãããã³ã°ãã¹ã±ãŒãªã³ã°ãå転ãåæãªã©ã®ãããªç·šéæ©èœãå®è£
ã§ããŸãããã¯ã»ã«ããŒã¿ãçŽæ¥æäœããããšã§ãã«ã¹ã¿ã ãã©ã³ãžã·ã§ã³ããšãã§ã¯ããäœæã§ããŸãã
äŸãã°ããã¯ã»ã«ããŒã¿ã®äžéšã®ã¿ãæ°ããVideoFrameã«ã³ããŒããããšã§VideoFrameãã¯ãããã§ããŸããããã«å¿ããŠlayoutã®ãªãã»ãããšã¹ãã©ã€ãã調æŽããŸãã
4. ã«ã¹ã¿ã ã³ãŒããã¯ãšãã©ã³ã¹ã³ãŒãã£ã³ã°
WebCodecsã¯AV1ãVP9ãH.264ãªã©ã®äžè¬çãªã³ãŒããã¯ãå
èµã§ãµããŒãããŠããŸãããã«ã¹ã¿ã ã³ãŒããã¯ããã©ã³ã¹ã³ãŒãã£ã³ã°ãã€ãã©ã€ã³ãå®è£
ããããã«ã䜿çšã§ããŸãããšã³ã³ãŒããšãã³ãŒãã®ããã»ã¹ãèªåã§åŠçããå¿
èŠããããŸãããVideoFrameãã¬ãŒã³ã䜿çšãããšçã®ãã¯ã»ã«ããŒã¿ã«ã¢ã¯ã»ã¹ããŠæäœã§ããŸããããã¯ãããããªãããªãã©ãŒããããç¹æ®ãªãšã³ã³ãŒãã£ã³ã°èŠä»¶ã«åœ¹ç«ã€å¯èœæ§ããããŸãã
5. é«åºŠãªåæ
åºç€ãšãªããã¯ã»ã«ããŒã¿ã«ã¢ã¯ã»ã¹ããããšã§ããããªã³ã³ãã³ãã®è©³çްãªåæãå®è¡ã§ããŸããããã«ã¯ãã·ãŒã³ã®å¹³åèŒåºŠã®æž¬å®ãäž»èŠãªè²ã®ç¹å®ãã·ãŒã³ã³ã³ãã³ãã®å€åã®æ€åºãªã©ã®ã¿ã¹ã¯ãå«ãŸããŸããããã«ãããã»ãã¥ãªãã£ãç£èŠããŸãã¯ã³ã³ãã³ãåæã®ããã®é«åºŠãªãããªåæã¢ããªã±ãŒã·ã§ã³ãå¯èœã«ãªããŸãã
CanvasãšWebGLã®æäœ
VideoFrameãã¬ãŒã³å
ã®ãã¯ã»ã«ããŒã¿ãçŽæ¥æäœããããšãã§ããŸãããå€ãã®å Žåãçµæãç»é¢ã«ã¬ã³ããªã³ã°ããå¿
èŠããããŸããCanvasImageBitmapã€ã³ã¿ãŒãã§ãŒã¹ã¯ãVideoFrameãš<canvas>èŠçŽ ã®éã®æ©æž¡ããæäŸããŸããVideoFrameããCanvasImageBitmapãäœæããdrawImage()ã¡ãœããã䜿çšããŠãã£ã³ãã¹ã«æç»ã§ããŸãã
async function renderVideoFrameToCanvas(videoFrame, canvas) {
const bitmap = await createImageBitmap(videoFrame);
const ctx = canvas.getContext('2d');
ctx.drawImage(bitmap, 0, 0, canvas.width, canvas.height);
bitmap.close(); // ãããããããªãœãŒã¹ãè§£æŸ
videoFrame.close(); // VideoFrameãªãœãŒã¹ãè§£æŸ
}
ããé«åºŠãªã¬ã³ããªã³ã°ã«ã¯ãWebGLã䜿çšã§ããŸããVideoFrameãã¬ãŒã³ããWebGLãã¯ã¹ãã£ã«ãã¯ã»ã«ããŒã¿ãã¢ããããŒãããã·ã§ãŒããŒã䜿çšããŠãšãã§ã¯ãã倿ãé©çšã§ããŸããããã«ããã髿§èœãªãããªåŠçã®ããã«GPUãæŽ»çšã§ããŸãã
ããã©ãŒãã³ã¹ã«é¢ããèæ ®äºé
çã®ãã¯ã»ã«ããŒã¿ãæ±ãããšã¯èšç®éçŽçã§ãããããããã©ãŒãã³ã¹ã®æé©åãèæ ®ããããšãéèŠã§ãã以äžã«ããã€ãã®ãã³ãã瀺ããŸãïŒ
- ã³ããŒãæå°éã«æããïŒãã¯ã»ã«ããŒã¿ã®äžå¿ èŠãªã³ããŒãé¿ããŠãã ãããå¯èœãªéããã€ã³ãã¬ãŒã¹æäœãå®è¡ããããã«ããŠãã ããã
- WebAssemblyã䜿çšããïŒã³ãŒãã®ããã©ãŒãã³ã¹ãéèŠãªã»ã¯ã·ã§ã³ã«ã¯ãWebAssemblyã®äœ¿çšãæ€èšããŠãã ãããWebAssemblyã¯ãèšç®éçŽçãªã¿ã¹ã¯ã«å¯ŸããŠãã€ãã£ãã«è¿ãããã©ãŒãã³ã¹ãæäŸã§ããŸãã
- ã¡ã¢ãªã¬ã€ã¢ãŠããæé©åããïŒã¢ããªã±ãŒã·ã§ã³ã«é©ãããã¯ã»ã«ãã©ãŒããããšã¡ã¢ãªã¬ã€ã¢ãŠããéžæããŠãã ãããåã ã®è²æåã«é »ç¹ã«ã¢ã¯ã»ã¹ããå¿ èŠããªãå Žåã¯ãããã¯åœ¢åŒïŒäŸïŒRGBAïŒã®äœ¿çšãæ€èšããŠãã ããã
- OffscreenCanvasã䜿çšããïŒããã¯ã°ã©ãŠã³ãåŠçã«ã¯ãã¡ã€ã³ã¹ã¬ããããããã¯ããªãããã«
OffscreenCanvasã䜿çšããŠãã ããã - ã³ãŒãããããã¡ã€ãªã³ã°ããïŒãã©ãŠã¶ã®éçºè ããŒã«ã䜿çšããŠã³ãŒãããããã¡ã€ãªã³ã°ããããã©ãŒãã³ã¹ã®ããã«ããã¯ãç¹å®ããŠãã ããã
ãã©ãŠã¶ã®äºææ§
WebCodecsãšVideoFrame APIã¯ãChromeãFirefoxãSafariãå«ãã»ãšãã©ã®çŸä»£çãªãã©ãŠã¶ã§ãµããŒããããŠããŸãããã ãããµããŒãã®ã¬ãã«ã¯ãã©ãŠã¶ã®ããŒãžã§ã³ããªãã¬ãŒãã£ã³ã°ã·ã¹ãã ã«ãã£ãŠç°ãªãå ŽåããããŸããMDN Web Docsã®ãããªãµã€ãã§ææ°ã®ãã©ãŠã¶äºææ§ããŒãã«ã確èªãã䜿çšããŠããæ©èœãã¿ãŒã²ãããã©ãŠã¶ã§ãµããŒããããŠããããšã確èªããŠãã ãããã¯ãã¹ãã©ãŠã¶äºææ§ã®ããã«ãæ©èœæ€åºãæšå¥šãããŸãã
ããããèœãšã穎ãšãã©ãã«ã·ã¥ãŒãã£ã³ã°
VideoFrameãã¬ãŒã³ãæ±ãéã«é¿ããã¹ãäžè¬çãªèœãšã穎ãããã€ã玹ä»ããŸãïŒ
- äžæ£ãªã¬ã€ã¢ãŠãïŒ
layouté åããã¯ã»ã«ããŒã¿ã®ã¡ã¢ãªã¬ã€ã¢ãŠããæ£ç¢ºã«èšè¿°ããŠããããšã確èªããŠãã ãããäžæ£ãªãªãã»ãããã¹ãã©ã€ãã¯ãç»åã®ç Žæã«ã€ãªããå¯èœæ§ããããŸãã - ãã¯ã»ã«ãã©ãŒãããã®äžäžèŽïŒ
copyToã¡ãœããã§æå®ãããã¯ã»ã«ãã©ãŒãããããVideoFrameã®å®éã®ãã©ãŒããããšäžèŽããŠããããšã確èªããŠãã ããã - ã¡ã¢ãªãªãŒã¯ïŒäœ¿ãçµãã£ãããå¿
ã
VideoFrameãªããžã§ã¯ããšCanvasImageBitmapãªããžã§ã¯ããéããŠãåºç€ãšãªããªãœãŒã¹ãè§£æŸããŠãã ãããããããªããšãã¡ã¢ãªãªãŒã¯ã«ã€ãªããå¯èœæ§ããããŸãã - éåææäœïŒ
copyToã¯éåææäœã§ããããšãå¿ããªãã§ãã ããããã¯ã»ã«ããŒã¿ã«ã¢ã¯ã»ã¹ããåã«ãã³ããŒæäœãå®äºããããã«awaitã䜿çšããŠãã ããã - ã»ãã¥ãªãã£å¶éïŒã¯ãã¹ãªãªãžã³ã®ãããªãããã¯ã»ã«ããŒã¿ã«ã¢ã¯ã»ã¹ããéã«é©çšãããå¯èœæ§ã®ããã»ãã¥ãªãã£å¶éã«æ³šæããŠãã ããã
äŸïŒYUVããRGBãžã®å€æ
ããè€éãªäŸãèããŠã¿ãŸãããïŒYUV420 VideoFrameãRGB VideoFrameã«å€æããŸããããã«ã¯ãYãUãVãã¬ãŒã³ãèªã¿åããããããRGBå€ã«å€æããæ°ããRGB VideoFrameãäœæããããšãå«ãŸããŸãã
ãã®å€æã¯ã以äžã®åŒã䜿çšããŠå®è£ ã§ããŸãïŒ
R = Y + 1.402 * (Cr - 128)
G = Y - 0.34414 * (Cb - 128) - 0.71414 * (Cr - 128)
B = Y + 1.772 * (Cb - 128)
ãã¡ããã³ãŒãã§ãïŒ
async function convertYUV420ToRGBA(videoFrame) {
const width = videoFrame.codedWidth;
const height = videoFrame.codedHeight;
const yPlaneSize = width * height;
const uvPlaneSize = width * height / 4;
const yuvBuffer = new ArrayBuffer(yPlaneSize + 2 * uvPlaneSize);
const yuvPlanes = new Uint8ClampedArray(yuvBuffer);
const layout = [
{ offset: 0, stride: width }, // Yãã¬ãŒã³
{ offset: yPlaneSize, stride: width / 2 }, // Uãã¬ãŒã³
{ offset: yPlaneSize + uvPlaneSize, stride: width / 2 } // Vãã¬ãŒã³
];
await videoFrame.copyTo(yuvPlanes, {
format: 'I420',
codedWidth: width,
codedHeight: height,
layout: layout
});
const rgbaBuffer = new ArrayBuffer(width * height * 4);
const rgba = new Uint8ClampedArray(rgbaBuffer);
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
const yIndex = y * width + x;
const uIndex = Math.floor(y / 2) * (width / 2) + Math.floor(x / 2) + yPlaneSize;
const vIndex = Math.floor(y / 2) * (width / 2) + Math.floor(x / 2) + yPlaneSize + uvPlaneSize;
const Y = yuvPlanes[yIndex];
const U = yuvPlanes[uIndex] - 128;
const V = yuvPlanes[vIndex] - 128;
let R = Y + 1.402 * V;
let G = Y - 0.34414 * U - 0.71414 * V;
let B = Y + 1.772 * U;
R = Math.max(0, Math.min(255, R));
G = Math.max(0, Math.min(255, G));
B = Math.max(0, Math.min(255, B));
const rgbaIndex = y * width * 4 + x * 4;
rgba[rgbaIndex] = R;
rgba[rgbaIndex + 1] = G;
rgba[rgbaIndex + 2] = B;
rgba[rgbaIndex + 3] = 255; // ã¢ã«ãã¡
}
}
const newFrame = new VideoFrame(rgba, {
format: 'RGBA',
codedWidth: width,
codedHeight: height,
timestamp: videoFrame.timestamp,
duration: videoFrame.duration
});
videoFrame.close(); // å
ã®ãã¬ãŒã ãè§£æŸ
return newFrame;
}
ãã®äŸã¯ãVideoFrameãã¬ãŒã³ãæ±ãããšã®å匷ããšè€éãã瀺ããŠããŸãããã¯ã»ã«ãã©ãŒããããã¡ã¢ãªã¬ã€ã¢ãŠããè²ç©ºé倿ã«ã€ããŠã®ååãªçè§£ãå¿
èŠã§ãã
çµè«
WebCodecsã®VideoFrameãã¬ãŒã³APIã¯ããã©ãŠã¶ã§ã®ãããªåŠçã«å¯Ÿããæ°ããªã¬ãã«ã®å¶åŸ¡ãå¯èœã«ããŸãããã¯ã»ã«ããŒã¿ã«çŽæ¥ã¢ã¯ã»ã¹ãæäœããæ¹æ³ãçè§£ããããšã§ããªã¢ã«ã¿ã€ã ãããªãšãã§ã¯ããã³ã³ãã¥ãŒã¿ããžã§ã³ããããªç·šéãªã©ã®ããã®é«åºŠãªã¢ããªã±ãŒã·ã§ã³ãäœæã§ããŸããVideoFrameãã¬ãŒã³ã®æ±ãã¯é£ãããããããŸããããåŸãããæœåšçãªå ±é
¬ã¯å€§ããã§ããWebCodecsãé²åãç¶ããã«ã€ããŠãã¡ãã£ã¢ãæ±ããŠã§ãéçºè
ã«ãšã£ãŠäžå¯æ¬ ãªããŒã«ã«ãªãããšã¯ééããªãã§ãããã
VideoFrameãã¬ãŒã³APIã詊ããŠããã®èœåãæ¢æ±ããããšããå§ãããŸããæ ¹æ¬çãªååãçè§£ãããã¹ããã©ã¯ãã£ã¹ãé©çšããããšã§ããã©ãŠã¶ã§å¯èœãªããšã®éçãæŒãåºããã驿°çã§é«æ§èœãªãããªã¢ããªã±ãŒã·ã§ã³ãäœæã§ããŸãã
ãããªãåŠç¿ã®ããã«
- WebCodecsã«é¢ããMDN Web Docs
- WebCodecs仿§
- GitHubäžã®WebCodecsãµã³ãã«ã³ãŒããªããžããªã